# 核支持向量机

'''
核支持向量机（SVM）是一种可以扩展到复杂模型的算法，适用于无法通过输入空间的超平面进行定义的情况。
虽然SVM可以用于分类和回归，但本文仅介绍其在分类中的应用，通常在SVC中实现。
类似的概念也用于支持向量回归，通常在SVR中实现
'''
import matplotlib.pyplot as plt
import mglearn
import numpy as np
from sklearn.datasets import make_blobs
from sklearn.model_selection import train_test_split
from sklearn.svm import LinearSVC

# 线性模型与非线性特征
'''
线性模型在低维空间中可能受到限制，因为线和平面的灵活性有限。
为了提高模型的灵活性，可以添加更多的特征，例如输入特征的交互项或多项式
'''

# 下面来看常用到的模拟数据集
X,y = make_blobs(centers=4,random_state=8)
y = y%2
mglearn.discrete_scatter(X[:,0],X[:,1],y)
plt.xlabel("Feature 0")
plt.ylabel("Feature 1")
# plt.show()

# 用于分类的线性模型只能用一条直线来划分数据点，对这个数据集无法给出较好的结果
linear_svm = LinearSVC().fit(X,y)
mglearn.plots.plot_2d_separator(linear_svm,X)
mglearn.discrete_scatter(X[:,0],X[:,1],y)
plt.xlabel("Feature 0")
plt.ylabel("Feature 1")
# plt.show()

'''
现在我们对输入特征进行扩展，比如说添加第二个特征的平方（feature1 ** 2）作为一个
新特征。现在我们将每个数据点表示为三维点 (feature0, feature1, feature1 ** 2)，而
不是二维点 (feature0, feature1)。
'''
# 添加第二个特征的平方，作为一个新特征
X_new = np.hstack([X, X[:, 1:] ** 2])
from mpl_toolkits.mplot3d import Axes3D, axes3d
figure = plt.figure()
# 3D可视化
ax = figure.add_subplot(111, projection='3d')  # 使用推荐的方式创建三维坐标轴
# 首先画出所有y == 0的点，然后画出所有y == 1的点
mask = y == 0
ax.scatter(X_new[mask, 0], X_new[mask, 1], X_new[mask, 2], c='b', s=60)
ax.scatter(X_new[~mask, 0], X_new[~mask, 1], X_new[~mask, 2], c='r', marker='^', s=60)
ax.set_xlabel("feature0")
ax.set_ylabel("feature1")
ax.set_zlabel("feature1 ** 2")
# plt.show()

# 在数据的新表示中，现在可以用线性模型（三维空间中的平面）将这两个类别分开。我们
# 可以用线性模型拟合扩展后的数据来验证这一点

# 创建新的特征矩阵
X_new = np.hstack([X, X[:, 1:] ** 2])

# 训练线性 SVM 模型
linear_svm_3d = LinearSVC().fit(X_new, y)
coef, intercept = linear_svm_3d.coef_.ravel(), linear_svm_3d.intercept_

# 创建图形和三维坐标轴
figure = plt.figure()
ax = figure.add_subplot(111, projection='3d')

# 显示线性决策边界
xx = np.linspace(X_new[:, 0].min() - 2, X_new[:, 0].max() + 2, 50)
yy = np.linspace(X_new[:, 1].min() - 2, X_new[:, 1].max() + 2, 50)
XX, YY = np.meshgrid(xx, yy)
ZZ = (coef[0] * XX + coef[1] * YY + intercept) / -coef[2]
ax.plot_surface(XX, YY, ZZ, rstride=8, cstride=8, alpha=0.3, color='g')

# 绘制数据点
mask = y == 0
ax.scatter(X_new[mask, 0], X_new[mask, 1], X_new[mask, 2], c='b', s=60)
ax.scatter(X_new[~mask, 0], X_new[~mask, 1], X_new[~mask, 2], c='r', marker='^', s=60)

# 设置轴标签
ax.set_xlabel("feature0")
ax.set_ylabel("feature1")
ax.set_zlabel("feature1 ** 2")

# 显示图形
# plt.show()
# 如果将线性 SVM 模型看作原始特征的函数，那么它实际上已经不是线性的了。它不是一
# 条直线，而是一个椭圆，你可以在下图中看出

ZZ = YY ** 2
dec = linear_svm_3d.decision_function(np.c_[XX.ravel(), YY.ravel(), ZZ.ravel()])
plt.contourf(XX, YY, dec.reshape(XX.shape), levels=[dec.min(), 0, dec.max()],
 cmap=mglearn.cm2, alpha=0.5)
mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
plt.xlabel("Feature 0")
plt.ylabel("Feature 1")
# plt.show()

# 核技巧
'''
核技巧是一种强大的技术，它允许我们在高维空间中进行学习，而无需显式地计算高维特征表示。
这种方法在支持向量机（SVM）等算法中得到了广泛应用。以下是关于核技巧和常用核函数的一些关键点：

核技巧的优势
 处理非线性关系：核技巧使得线性模型能够处理非线性数据。通过将数据映射到高维空间，原本线性不可分的数据可能变得线性可分。
 避免显式特征扩展：不需要显式地计算高维特征表示，从而节省了计算资源和存储空间。
 灵活性：可以使用不同的核函数来适应不同的数据分布和问题需求。
常用核函数
 1.多项式核：
  定义：多项式核可以计算原始特征的所有可能多项式组合，直到某一阶数。例如，多项式核可以计算feature1^2 × feature2^5等组合。
  特点：多项式核的阶数越高，映射后的特征空间维度越高，模型的复杂度也越高。
 2.径向基函数（RBF）核：
  定义：RBF核，也称为高斯核，对应于无限维的特征空间。它通过计算数据点之间的相似性来实现特征映射。
  特点：RBF核考虑了所有阶数的所有可能多项式，但随着阶数的增加，特征的重要性逐渐减小。它在处理复杂非线性关系时非常有效。
实践中的应用
 选择合适的核函数：根据数据的特性和问题需求选择合适的核函数。对于大多数非线性问题，RBF核是一个很好的默认选择。
 参数调整：核函数通常有一些参数需要调整，例如RBF核的γ参数。γ值越大，模型对数据的局部变化越敏感，但可能导致过拟合。
 模型解释性：虽然核技巧使得模型能够处理复杂的非线性关系，但它也降低了模型的解释性。因此，在需要解释模型决策的情况下，可能需要考虑其他方法。
在实践中，核SVM的数学细节并不是最重要的，关键在于如何选择合适的核函数和参数来适应具体的问题。
通过实验和交叉验证，可以找到最佳的核函数和参数组合，从而提高模型的预测性能。
'''

# 理解SVM
'''
支持向量机（SVM）是一种强大的分类算法，其核心思想是找到一个决策边界，使得不同类别的数据点之间的间隔最大化。以下是关于SVM训练过程和预测机制的详细解释：

训练过程
 决策边界的确定：SVM的目标是找到一个超平面，使得两个类别之间的间隔最大化。这个超平面被称为最优超平面。
 支持向量：在训练过程中，SVM识别出那些位于类别边界上的数据点，这些点被称为支持向量。支持向量是定义决策边界的关键点，因为它们决定了最优超平面的位置和方向。
 优化问题：SVM通过解决一个凸优化问题来找到最优超平面。这个优化问题的目标是最大化间隔，同时确保数据点被正确分类。
预测机制
 距离测量：对于新样本点的预测，SVM需要测量该点与每个支持向量之间的距离。这个距离用于评估新样本点相对于决策边界的相对位置。
 高斯核函数：当使用高斯核（RBF核）时，核函数用于计算数据点之间的相似性。计算公式如下：
 krbf (x1, x2) = exp (-γ‖x1 - x2‖2) γ是控制高斯核宽度的参数。
 分类决策：分类决策是基于新样本点与支持向量之间的距离以及支持向量的重要性（保存在SVC的dual_coef_属性中）来做出的。
  通过计算新样本点与支持向量的核函数值，并结合支持向量的权重，SVM可以确定新样本点的类别。
'''

# 图 2-41 是支持向量机对一个二维二分类数据集的训练结果。决策边界用黑色表示，支持向
# 量是尺寸较大的点。下列代码将在 forge 数据集上训练 SVM 并创建此图：

from sklearn.svm import SVC
# 用于生成一个用于演示和教学的合成数据集。这个数据集通常用于展示机器学习模型的决策边界和分类效果。
X, y = mglearn.tools.make_handcrafted_dataset()
# kernel='rbf': 指定核函数为径向基函数（Radial Basis Function, RBF）。RBF 核是一种常用的核函数，
#  能够将数据映射到高维空间，从而使得非线性可分的数据在高维空间中线性可分
# C=10: 这是正则化参数，控制误差项的惩罚程度。较大的 C 值意味着模型会更严格地惩罚分类错误，
#  从而使得决策边界更贴近数据点，但也可能导致过拟合。较小的 C 值则会使模型更平滑，容忍更多的分类错误.
# gamma=0.1: 这是 RBF 核函数的参数，控制映射后的特征空间的维度。较大的 gamma 值意味着映射后的特征空间维度更高，
#  模型可以更灵活地拟合数据，但也可能导致过拟合.较小的 gamma 值则会使模型更平滑
svm = SVC(kernel='rbf', C=10, gamma=0.1).fit(X, y)
mglearn.plots.plot_2d_separator(svm, X, eps=.5)
mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
# 画出支持向量
# upport_vectors_ 是一个属性，用于获取模型中的支持向量。支持向量是训练数据中对构建决策边界有重要贡献的数据点
sv = svm.support_vectors_
# 支持向量的类别标签由dual_coef_的正负号给出
sv_labels = svm.dual_coef_.ravel() > 0
mglearn.discrete_scatter(sv[:, 0], sv[:, 1], sv_labels, s=15, markeredgewidth=3)
plt.xlabel("Feature 0")
plt.ylabel("Feature 1")
# plt.show()

# 在这个例子中，SVM 给出了非常平滑且非线性（不是直线）的边界。这里我们调节了两
# 个参数：C 参数和 gamma 参数，下面我们将详细讨论。

# SVM调参
'''
gamma 参数是上一节给出的公式中的参数，用于控制高斯核的宽度。它决定了点与点之间
“靠近”是指多大的距离。C 参数是正则化参数，与线性模型中用到的类似。它限制每个点
的重要性（或者更确切地说，每个点的 dual_coef_）

在支持向量机（SVM）中，
γ (gamma) 和C是两个非常重要的参数，它们共同决定了模型的性能和复杂度。

γ 参数
作用：
 γ是高斯核（RBF核）中的参数，用于控制核函数的宽度。它决定了数据点之间的相似性是如何随着距离变化的。
影响：
 较大的γ：
  意味着核函数的宽度较小，数据点之间的相似性在较小的距离内迅速减小。这使得模型对数据的局部变化更敏感，
  可能导致过拟合，尤其是在数据点密集的区域。
 较小的γ：
  意味着核函数的宽度较大，数据点之间的相似性在较大的距离内仍然较高。这使得模型对数据的全局变化更敏感，
  可能导致欠拟合，尤其是在数据点稀疏的区域。

C 参数
作用：
 C 是正则化参数，用于控制模型对误分类的惩罚程度。它决定了模型在最大化间隔和最小化分类误差之间的平衡。
影响：
 较大的C：
  意味着模型对误分类的惩罚更大，会尝试找到一个能够正确分类所有训练数据点的超平面。这可能导致模型过于复杂，从而过拟合。
 较小的C：
  意味着模型对误分类的惩罚较小，会倾向于找到一个间隔更大的超平面，即使这意味着一些训练数据点被误分类。这可能导致模型过于简单，从而欠拟合。

参数调整
 交叉验证：通常使用交叉验证来选择合适的γ和C值。通过在不同的参数组合下评估模型的性能，可以找到一个在训练集和测试集上都表现良好的参数组合。
 网格搜索：可以使用网格搜索等方法系统地搜索参数空间，以找到最佳的参数组合。
通过合理调整γ和C参数，可以有效地控制SVM模型的复杂度和泛化能力，使其在不同的数据集上都能取得良好的性能。
'''

# 下面来看一下，改变这些参数时会发生什么

fig, axes = plt.subplots(3, 3, figsize=(15, 10))
for ax, C in zip(axes, [-1, 0, 3]):
 for a, gamma in zip(ax, range(-1, 2)):
  mglearn.plots.plot_svm(log_C=C, log_gamma=gamma, ax=a)
axes[0, 0].legend(["class 0", "class 1", "sv class 0", "sv class 1"],
 ncol=4, loc=(.9, 1.2))
# plt.show()


'''
从左到右，我们将参数 gamma 的值从 0.1 增加到 10。gamma 较小，说明高斯核的半径较大，
许多点都被看作比较靠近。这一点可以在图中看出：左侧的图决策边界非常平滑，越向右
的图决策边界更关注单个点。小的 gamma 值表示决策边界变化很慢，生成的是复杂度较低
的模型，而大的 gamma 值则会生成更为复杂的模型。
从上到下，我们将参数 C 的值从 0.1 增加到 1000。与线性模型相同，C 值很小，说明模型
非常受限，每个数据点的影响范围都有限。你可以看到，左上角的图中，决策边界看起来
几乎是线性的，误分类的点对边界几乎没有任何影响。再看左下角的图，增大 C 之后这些
点对模型的影响变大，使得决策边界发生弯曲来将这些点正确分类。
'''

# 我们将 RBF 核 SVM 应用到乳腺癌数据集上。默认情况下，C=1，gamma=1/n_features：
from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
cancer.data, cancer.target, random_state=0)
svc = SVC()
svc.fit(X_train, y_train)
print("Accuracy on training set: {:.2f}".format(svc.score(X_train, y_train)))
print("Accuracy on test set: {:.2f}".format(svc.score(X_test, y_test)))
# Accuracy on training set: 0.90
# Accuracy on test set: 0.94

'''
虽然 SVM 的表现通常都很好，但它对参数的设定和数据的缩放非常敏感。特别地，
它要求所有特征有相似的变化范围。

对于数据及的特征有完全不同的数量级导致对核 SVM 却有极大影响。我们来研究处理这个问
题的几种方法。
'''

# 为SVM预处理数据
'''
解决这个问题的一种方法就是对每个特征进行缩放，使其大致都位于同一范围。核 SVM
常用的缩放方法就是将所有特征缩放到 0 和 1 之间。我们将在第 3 章学习如何使用
MinMaxScaler 预处理方法来做到这一点，到时会给出更多细节。现在我们来“人工”做到
这一点：
'''

# 计算训练集中每个特征的最小值
min_on_training = X_train.min(axis=0)
# 计算训练集中每个特征的范围（最大值-最小值）
range_on_training = (X_train - min_on_training).max(axis=0)
# 减去最小值，然后除以范围
# 这样每个特征都是min=0和max=1
X_train_scaled = (X_train - min_on_training) / range_on_training
print("Minimum for each feature\n{}".format(X_train_scaled.min(axis=0)))
print("Maximum for each feature\n {}".format(X_train_scaled.max(axis=0)))

# Minimum for each feature
# [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
#  0. 0. 0. 0. 0. 0.]
# Maximum for each feature
#  [1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.
#  1. 1. 1. 1. 1. 1.]

# 利用训练集的最小值和范围对测试集做相同的变换
X_test_scaled = (X_test - min_on_training) / range_on_training

svc = SVC()
svc.fit(X_train_scaled, y_train)
print("Accuracy on training set: {:.3f}".format(
 svc.score(X_train_scaled, y_train)))
print("Accuracy on test set: {:.3f}".format(svc.score(X_test_scaled, y_test)))
# Accuracy on training set: 0.984
# Accuracy on test set: 0.972

'''
数据缩放的作用很大！实际上模型现在处于欠拟合的状态，因为训练集和测试集的性能非
常接近。
'''

# 优点、缺点和参数
'''
核支持向量机（SVM）是一种强大的分类和回归模型，具有许多优点，但也存在一些局限性。以下是关于核SVM的一些总结：

优点
 强大的决策边界：SVM能够学习复杂的决策边界，即使在低维数据（特征较少）上也能表现良好。
 适用于高维数据：在高维数据（特征较多）上，SVM通过核技巧有效地处理数据，而不需要显式地进行特征扩展。
 鲁棒性：对噪声和异常值具有一定的鲁棒性，因为它关注的是支持向量，而不是所有数据点。
缺点
 样本规模限制：SVM在处理大规模数据集时可能会遇到挑战。当样本个数达到100,000或更大时，SVM的训练时间和内存使用可能会显著增加。
 数据预处理和调参复杂：需要仔细进行数据预处理（如特征缩放）和参数调整，以获得最佳性能。
 模型解释性差：SVM模型的决策过程相对复杂，难以直观地解释和向非专家解释。
 
适用场景
 特征测量单位相似且范围接近：当所有特征的测量单位相似（如都是像素密度）且范围差不多时，SVM是一个不错的选择。
 需要高精度分类：对于需要高精度分类的任务，SVM可以提供优秀的性能。
 
尽管SVM在某些方面存在局限性，但在许多机器学习任务中，它仍然是一个值得尝试的模型，尤其是在特征测量单位相似且范围接近的情况下。
对于大规模数据集或需要高解释性的应用，可以考虑使用基于树的模型，如随机森林或梯度提升树，因为它们通常需要较少的预处理和调参工作。
'''
